using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq.Expressions;
using System.Threading;
using FluentSilverlight.Expressions;
using FluentSilverlight.Utility.Reflection;

namespace FluentSilverlight.MVVM
{
    public abstract class ViewModelBase : IAutoNotifyPropertyChanged, IViewModel
    {
        private readonly SynchronizationContext syncContext;

        public event PropertyChangedEventHandler PropertyChanged;

        protected ViewModelBase()
        {
            syncContext = SynchronizationContext.Current;
        }

        public virtual void OnPropertyChanged(string propertyName)
        {
            if (String.IsNullOrEmpty(propertyName))
                throw new ArgumentNullException("propertyName");

            if (PropertyChanged == null) return;
            var eventArgs = new PropertyChangedEventArgs(propertyName);
            if (syncContext == null)
                PropertyChanged(this, eventArgs);
            else
                syncContext.Post(state => PropertyChanged(this, eventArgs), null);
        }

        // used by the interceptor
        public static void OnPropertyChangedStatic(ViewModelBase model, PropertyChangedEventArgs args)
        {
            model.OnPropertyChanged(args.PropertyName);
        }

        IOpinionatedControl IViewModel.OpinionatedControl { get; set; }
        public abstract void Initialize();
        public abstract void InitializeData();
    }

    public abstract class ViewModelBase<TModel, TView> : ViewModelBase, IViewModel<TView>, IPropertyChangedInterceptor 
        where TModel : ViewModelBase<TModel, TView>
        where TView : IOpinionatedControl 
    {
        public TView View { get; set; }

        IOpinionatedControl IViewModel.OpinionatedControl
        {
            get { return View; }
            set { View = (TView)value; }
        }

        protected virtual void OnPropertyChanged(Expression<Func<TModel, object>> expression)
        {
            var accessor = ReflectionHelper.GetAccessor(expression);
            var name = accessor.Name;
            OnPropertyChanged(name);
        }

        public override void OnPropertyChanged(string propertyName)
        {
            base.OnPropertyChanged(propertyName);

            InvokeInterceptionActions(propertyName);
        }

        public static bool IsEqual(string fieldName, Expression<Func<TModel, object>> expression)
        {
            var accessor = ReflectionHelper.GetAccessor(expression);
            return accessor.Name.Equals(fieldName);
        }

        private readonly Dictionary<string, List<Action>> interceptorActions = new Dictionary<string, List<Action>>();
        private string lastInterceptedPropertyName;

        public IPropertyChangedInterceptor WhenPropertyChanged(Expression<Func<TModel, object>> expression)
        {
            lastInterceptedPropertyName = ReflectionHelper.GetAccessor(expression).Name;
            return this;
        }

        IPropertyChangedInterceptor IPropertyChangedInterceptor.Do(Action action)
        {
            if (!interceptorActions.ContainsKey(lastInterceptedPropertyName))
                interceptorActions.Add(lastInterceptedPropertyName, new List<Action>());
            interceptorActions[lastInterceptedPropertyName].Add(action);
            return this;
        }

        private void InvokeInterceptionActions(string propertyName)
        {
            if (!interceptorActions.ContainsKey(propertyName)) return;
            interceptorActions[propertyName].ForEach(action => action());
        }
    }
}